#include "OpenSLEngine.h"
#include <stdlib.h>

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,"OPENSL_ENGINE.CPP", __VA_ARGS__)

using namespace std;


OpenSLEngine::OpenSLEngine()
        : m_musicVolume(0),
          m_effectVolume(0) {}

OpenSLEngine::~OpenSLEngine() {
    closeEngine();
}

/**********************************************************************************
 *   jni
 **********************************************************************************/
#define  CLASS_NAME "org/CrossApp/lib/CrossAppHelper"

typedef struct JniMethodInfo_ {
    JNIEnv *env;
    jclass classID;
    jmethodID methodID;
} JniMethodInfo;

extern "C" {
static JNIEnv *getJNIEnv(void) {

    JavaVM *jvm = CrossApp::JniHelper::getJavaVM();
    if (NULL == jvm) {
        LOGD("Failed to get JNIEnv. JniHelper::getJavaVM() is NULL");
        return NULL;
    }

    JNIEnv *env = NULL;
    // get jni environment
    jint ret = jvm->GetEnv((void **) &env, JNI_VERSION_1_4);

    switch (ret) {
        case JNI_OK :
            // Success!
            return env;

        case JNI_EDETACHED :
            // Thread not attached

            // TODO : If calling AttachCurrentThread() on a native thread
            // must call DetachCurrentThread() in future.
            // see: http://developer.android.com/guide/practices/design/jni.html

            if (jvm->AttachCurrentThread(&env, NULL) < 0) {
                LOGD("Failed to get the environment using AttachCurrentThread()");
                return NULL;
            } else {
                // Success : Attached and obtained JNIEnv!
                return env;
            }

        case JNI_EVERSION :
            // Cannot recover from this error
            LOGD("JNI interface version 1.4 not supported");
        default :
            LOGD("Failed to get the environment using GetEnv()");
            return NULL;
    }
}

static jclass getClassID(JNIEnv *pEnv) {
    jclass ret = pEnv->FindClass(CLASS_NAME);
    if (!ret) {
        LOGD("Failed to find class of %s", CLASS_NAME);
    }

    return ret;
}

static bool
getStaticMethodInfo(JniMethodInfo &methodinfo, const char *methodName, const char *paramCode) {
    jmethodID methodID = 0;
    JNIEnv *pEnv = 0;
    bool bRet = false;

    do {
        pEnv = getJNIEnv();
        if (!pEnv) {
            break;
        }

        jclass classID = getClassID(pEnv);

        methodID = pEnv->GetStaticMethodID(classID, methodName, paramCode);
        if (!methodID) {
            LOGD("Failed to find static method id of %s", methodName);
            break;
        }

        methodinfo.classID = classID;
        methodinfo.env = pEnv;
        methodinfo.methodID = methodID;

        bRet = true;
    } while (0);

    return bRet;
}
};


/*********************************************************************************
 *   helper
 ********************************************************************************/
#define PLAYSTATE_UNKNOWN 0
#define FILE_NOT_FOUND -1

#define ASSET_MANAGER_GETTER "getAssetManager"
#define LIBANDROID "libandroid.so"

#define MIN_VOLUME_MILLIBEL -4000
#define MAX_VOLUME_MILLIBEL 0
#define RANGE_VOLUME_MILLIBEL 4000

struct AudioPlayer {
    SLDataSource audioSrc;
    SLObjectItf fdPlayerObject;
    SLPlayItf fdPlayerPlay;
    SLSeekItf fdPlayerSeek;
    SLVolumeItf fdPlayerVolume;
} musicPlayer; /* for background music */

typedef map<unsigned int, vector<AudioPlayer *> *> EffectList;
typedef pair<unsigned int, vector<AudioPlayer *> *> Effect;

void *s_pAndroidHandle = NULL;
void *s_pOpenSLESHandle = NULL;

static EffectList &sharedList() {
    static EffectList s_List;
    return s_List;
}

unsigned int _Hash(const char *key) {
    unsigned int len = strlen(key);
    const char *end = key + len;
    unsigned int hash;

    for (hash = 0; key < end; key++) {
        hash *= 16777619;
        hash ^= (unsigned int) (unsigned char) toupper(*key);
    }
    return (hash);
}

SLInterfaceID getInterfaceID(const char *value) {
    // clear the error stack
    dlerror();
    SLInterfaceID *IID = (SLInterfaceID *) dlsym(s_pOpenSLESHandle, value);
    const char *errorInfo = dlerror();
    if (errorInfo) {
        LOGD("Get interface id: %s from OpenSL failed", errorInfo);
        IID = NULL;
    }
    return *IID;
}

void *getFuncPtr(const char *value) {
    // clear the error stack
    dlerror();
    void *funcPtr = dlsym(s_pOpenSLESHandle, value);
    const char *errorInfo = dlerror();
    if (errorInfo) {
        LOGD("Get function from OpenSL failed: %s", errorInfo);
        funcPtr = NULL;
    }
    return funcPtr;
}

int getFileDescriptor(const char *filename, off_t &start, off_t &length) {
    JniMethodInfo methodInfo;
    if (!getStaticMethodInfo(methodInfo, ASSET_MANAGER_GETTER,
                             "()Landroid/content/res/AssetManager;")) {
        methodInfo.env->DeleteLocalRef(methodInfo.classID);
        return FILE_NOT_FOUND;
    }
    jobject assetManager = methodInfo.env->CallStaticObjectMethod(methodInfo.classID,
                                                                  methodInfo.methodID);
    methodInfo.env->DeleteLocalRef(methodInfo.classID);

    AAssetManager *(*AAssetManager_fromJava)(JNIEnv *env, jobject assetManager);
    AAssetManager_fromJava = (AAssetManager *(*)(JNIEnv *env, jobject assetManager))
            dlsym(s_pAndroidHandle, "AAssetManager_fromJava");
    AAssetManager *mgr = AAssetManager_fromJava(methodInfo.env, assetManager);
    assert(NULL != mgr);

    AAsset *(*AAssetManager_open)(AAssetManager *mgr, const char *filename, int mode);
    AAssetManager_open = (AAsset *(*)(AAssetManager *mgr, const char *filename, int mode))
            dlsym(s_pAndroidHandle, "AAssetManager_open");
    AAsset *Asset = AAssetManager_open(mgr, filename, AASSET_MODE_UNKNOWN);
    if (NULL == Asset) {
        //LOGD("file not found! Stop preload file: %s", filename);
        return FILE_NOT_FOUND;
    }

    // open asset as file descriptor
    int (*AAsset_openFileDescriptor)(AAsset *asset, off_t *outStart, off_t *outLength);
    AAsset_openFileDescriptor = (int (*)(AAsset *asset, off_t *outStart, off_t *outLength))
            dlsym(s_pAndroidHandle, "AAsset_openFileDescriptor");
    int fd = AAsset_openFileDescriptor(Asset, &start, &length);
    assert(0 <= fd);

    void (*AAsset_close)(AAsset *asset);
    AAsset_close = (void (*)(AAsset *asset))
            dlsym(s_pAndroidHandle, "AAsset_close");
    AAsset_close(Asset);

    return fd;
}


/**********************************************************************************
 *   engine
 **********************************************************************************/
static SLObjectItf s_pEngineObject = NULL;
static SLEngineItf s_pEngineEngine = NULL;
static SLObjectItf s_pOutputMixObject = NULL;

bool createAudioPlayerBySource(AudioPlayer *player) {
    // configure audio sink
    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, s_pOutputMixObject};
    SLDataSink audioSnk = {&loc_outmix, NULL};

    // create audio player
    const SLInterfaceID ids[3] = {
            getInterfaceID("SL_IID_SEEK"), getInterfaceID("SL_IID_MUTESOLO"),
            getInterfaceID("SL_IID_VOLUME")};
    const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    SLresult result = (*s_pEngineEngine)->CreateAudioPlayer(s_pEngineEngine,
                                                            &(player->fdPlayerObject),
                                                            &(player->audioSrc), &audioSnk, 3, ids,
                                                            req);
    if (SL_RESULT_MEMORY_FAILURE == result) {
        return false;
    }

    // realize the player
    result = (*(player->fdPlayerObject))->Realize(player->fdPlayerObject, SL_BOOLEAN_FALSE);
    assert(SL_RESULT_SUCCESS == result);

    // get the play interface
    result = (*(player->fdPlayerObject))->GetInterface(player->fdPlayerObject,
                                                       getInterfaceID("SL_IID_PLAY"),
                                                       &(player->fdPlayerPlay));
    assert(SL_RESULT_SUCCESS == result);

    // get the volume interface
    result = (*(player->fdPlayerObject))->GetInterface(player->fdPlayerObject,
                                                       getInterfaceID("SL_IID_VOLUME"),
                                                       &(player->fdPlayerVolume));
    assert(SL_RESULT_SUCCESS == result);

    // get the seek interface
    result = (*(player->fdPlayerObject))->GetInterface(player->fdPlayerObject,
                                                       getInterfaceID("SL_IID_SEEK"),
                                                       &(player->fdPlayerSeek));
    assert(SL_RESULT_SUCCESS == result);

    return true;
}

bool initAudioPlayer(AudioPlayer *player, const char *filename) {
    // configure audio source
    off_t start, length;
    int fd = getFileDescriptor(filename, start, length);
    if (FILE_NOT_FOUND == fd) {
        FILE *fp = fopen(filename, "rb");
        if (fp) {
            SLDataLocator_URI loc_fd = {SL_DATALOCATOR_URI, (SLchar *) filename};
            SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, NULL,
                                             SL_CONTAINERTYPE_UNSPECIFIED};
            player->audioSrc.pLocator = &loc_fd;
            player->audioSrc.pFormat = &format_mime;
            return createAudioPlayerBySource(player);
        }
        LOGD("file not found! Stop preload file: %s", filename);
        return false;
    }
    SLDataLocator_AndroidFD loc_fd = {SL_DATALOCATOR_ANDROIDFD, fd, start, length};
    SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED};
    player->audioSrc.pLocator = &loc_fd;
    player->audioSrc.pFormat = &format_mime;

    return createAudioPlayerBySource(player);
}

void destroyAudioPlayer(AudioPlayer *player) {
    if (player && player->fdPlayerObject != NULL) {
        SLresult result;
        result = (*(player->fdPlayerPlay))->SetPlayState(player->fdPlayerPlay,
                                                         SL_PLAYSTATE_STOPPED);
        assert(SL_RESULT_SUCCESS == result);

        (*(player->fdPlayerObject))->Destroy(player->fdPlayerObject);
        player->fdPlayerObject = NULL;
        player->fdPlayerPlay = NULL;
        player->fdPlayerSeek = NULL;
        player->fdPlayerVolume = NULL;
    }
}

void OpenSLEngine::createEngine(void *pHandle) {
    assert(pHandle != NULL);
    s_pOpenSLESHandle = pHandle;

    // clear the error stack
    dlerror();
    s_pAndroidHandle = dlopen(LIBANDROID, RTLD_LAZY);
    const char *errorInfo = dlerror();
    if (errorInfo) {
        LOGD("%s", errorInfo);
        return;
    }

    SLresult result;
    if (s_pEngineObject == NULL) {
        // create engine
        SLresult (*slCreateEngine)(SLObjectItf *pEngine, SLuint32 numOptions,
                                   const SLEngineOption *pEngineOptions, SLuint32 numInterfaces,
                                   const SLInterfaceID *pInterfaceIds,
                                   const SLboolean *pInterfaceRequired);
        slCreateEngine = (SLresult (*)(SLObjectItf *pEngine, SLuint32 numOptions,
                                       const SLEngineOption *pEngineOptions, SLuint32 numInterfaces,
                                       const SLInterfaceID *pInterfaceIds,
                                       const SLboolean *pInterfaceRequired))
                getFuncPtr("slCreateEngine");
        result = slCreateEngine(&s_pEngineObject, 0, NULL, 0, NULL, NULL);
        assert(SL_RESULT_SUCCESS == result);

        // realize the engine
        result = (*s_pEngineObject)->Realize(s_pEngineObject, SL_BOOLEAN_FALSE);
        assert(SL_RESULT_SUCCESS == result);

        // get the engine interface, which is needed in order to create other objects
        result = (*s_pEngineObject)->GetInterface(s_pEngineObject, getInterfaceID("SL_IID_ENGINE"),
                                                  &s_pEngineEngine);
        assert(SL_RESULT_SUCCESS == result);

        // create output mix
        const SLInterfaceID ids[1] = {getInterfaceID("SL_IID_ENVIRONMENTALREVERB")};
        const SLboolean req[1] = {SL_BOOLEAN_FALSE};
        result = (*s_pEngineEngine)->CreateOutputMix(s_pEngineEngine, &s_pOutputMixObject, 1, ids,
                                                     req);
        assert(SL_RESULT_SUCCESS == result);

        // realize the output mix object in sync. mode
        result = (*s_pOutputMixObject)->Realize(s_pOutputMixObject, SL_BOOLEAN_FALSE);
        assert(SL_RESULT_SUCCESS == result);
    }
}

void OpenSLEngine::closeEngine() {
    // destroy background players
    destroyAudioPlayer(&musicPlayer);

    // destroy effect players
    vector<AudioPlayer *> *vec;
    EffectList::iterator p = sharedList().begin();
    while (p != sharedList().end()) {
        vec = p->second;
        for (vector<AudioPlayer *>::iterator iter = vec->begin(); iter != vec->end(); ++iter) {
            destroyAudioPlayer(*iter);
        }
        vec->clear();
        p++;
    }
    sharedList().clear();

    // destroy output mix interface
    if (s_pOutputMixObject) {
        (*s_pOutputMixObject)->Destroy(s_pOutputMixObject);
        s_pOutputMixObject = NULL;
    }

    // destroy opensl engine
    if (s_pEngineObject) {
        (*s_pEngineObject)->Destroy(s_pEngineObject);
        s_pEngineObject = NULL;
        s_pEngineEngine = NULL;
    }

    LOGD("%s", "engine destory");
}


/**********************************************************************************
 *   sound effect
 **********************************************************************************/
typedef struct _CallbackContext {
    vector<AudioPlayer *> *vec;
    AudioPlayer *player;
} CallbackContext;

void PlayOverEvent(SLPlayItf caller, void *pContext, SLuint32 playEvent) {
    CallbackContext *context = (CallbackContext *) pContext;
    if (playEvent == SL_PLAYEVENT_HEADATEND) {
        vector<AudioPlayer *>::iterator iter;
        for (iter = (context->vec)->begin(); iter != (context->vec)->end(); ++iter) {
            if (*iter == context->player) {
                (context->vec)->erase(iter);
                break;
            }
        }
        destroyAudioPlayer(context->player);
        free(context);
    }
}

void setSingleEffectVolume(AudioPlayer *player, SLmillibel volume) {
    SLresult result;
    result = (*(player->fdPlayerVolume))->SetVolumeLevel(player->fdPlayerVolume, volume);
    assert(result == SL_RESULT_SUCCESS);
}

int getSingleEffectState(AudioPlayer *player) {
    SLuint32 state = 0;
    SLresult result;
    result = (*(player->fdPlayerPlay))->GetPlayState(player->fdPlayerPlay, &state);
    assert(result == SL_RESULT_SUCCESS);

    return (int) state;
}

void setSingleEffectState(AudioPlayer *player, int state) {
    SLresult result;
    if (player->fdPlayerPlay != NULL) {
        // don't set to PAUSED state if it's already set to STOPPED state
        int oldState = getSingleEffectState(player);
        if (oldState == SL_PLAYSTATE_STOPPED && state == SL_PLAYSTATE_PAUSED) {
            return;
        }
        result = (*(player->fdPlayerPlay))->SetPlayState(player->fdPlayerPlay, state);
        assert(SL_RESULT_SUCCESS == result);
    }
}

void resumeSingleEffect(AudioPlayer *player) {
    int state = getSingleEffectState(player);
    // only resume the effect that has been paused
    if (state == SL_PLAYSTATE_PAUSED) {
        setSingleEffectState(player, SL_PLAYSTATE_PLAYING);
    }
}

bool OpenSLEngine::recreatePlayer(const char *filename) {
    unsigned int effectID = _Hash(filename);
    EffectList::iterator p = sharedList().find(effectID);
    vector<AudioPlayer *> *vec = p->second;
    AudioPlayer *newPlayer = new AudioPlayer();
    if (!initAudioPlayer(newPlayer, filename)) {
        LOGD("%s", "failed to recreate");
        return false;
    }
    vec->push_back(newPlayer);

    // set callback
    SLresult result;
    CallbackContext *context = new CallbackContext();
    context->vec = vec;
    context->player = newPlayer;
    result = (*(newPlayer->fdPlayerPlay))->RegisterCallback(newPlayer->fdPlayerPlay, PlayOverEvent,
                                                            (void *) context);
    assert(SL_RESULT_SUCCESS == result);

    result = (*(newPlayer->fdPlayerPlay))->SetCallbackEventsMask(newPlayer->fdPlayerPlay,
                                                                 SL_PLAYEVENT_HEADATEND);
    assert(SL_RESULT_SUCCESS == result);

    // set volume
    setSingleEffectVolume(newPlayer, m_effectVolume);
    setSingleEffectState(newPlayer, SL_PLAYSTATE_STOPPED);
    setSingleEffectState(newPlayer, SL_PLAYSTATE_PLAYING);

    // LOGD("vec count is %d of effectID %d", vec->size(), effectID);
    return true;
}

unsigned int OpenSLEngine::preloadEffect(const char *filename) {
    unsigned int nID = _Hash(filename);
    // if already exists
    EffectList::iterator p = sharedList().find(nID);
    if (p != sharedList().end()) {
        return nID;
    }

    AudioPlayer *player = new AudioPlayer();
    if (!initAudioPlayer(player, filename)) {
        free(player);
        return FILE_NOT_FOUND;
    }

    // set the new player's volume as others'
    setSingleEffectVolume(player, m_effectVolume);

    vector<AudioPlayer *> *vec = new vector<AudioPlayer *>;
    vec->push_back(player);
    sharedList().insert(Effect(nID, vec));
    return nID;
}

void OpenSLEngine::unloadEffect(const char *filename) {
    unsigned int nID = _Hash(filename);

    EffectList::iterator p = sharedList().find(nID);
    if (p != sharedList().end()) {
        vector<AudioPlayer *> *vec = p->second;
        for (vector<AudioPlayer *>::iterator iter = vec->begin(); iter != vec->end(); ++iter) {
            destroyAudioPlayer(*iter);
        }
        vec->clear();
        sharedList().erase(nID);
    }
}

int OpenSLEngine::getEffectState(unsigned int effectID) {
    int state = PLAYSTATE_UNKNOWN;
    EffectList::iterator p = sharedList().find(effectID);
    if (p != sharedList().end()) {
        vector<AudioPlayer *> *vec = p->second;
        // get the last player's state
        vector<AudioPlayer *>::reverse_iterator r_iter = vec->rbegin();
        state = getSingleEffectState(*r_iter);
    }
    return state;
}

void OpenSLEngine::setEffectState(unsigned int effectID, int state, bool isClear) {
    EffectList::iterator p = sharedList().find(effectID);
    if (p != sharedList().end()) {
        vector<AudioPlayer *> *vec = p->second;
        if (state == SL_PLAYSTATE_STOPPED || state == SL_PLAYSTATE_PAUSED) {
            // if stopped, clear the recreated players which are unused
            if (isClear) {
                setSingleEffectState(*(vec->begin()), state);
                vector<AudioPlayer *>::reverse_iterator r_iter = vec->rbegin();
                for (int i = 1, size = vec->size(); i < size; ++i) {
                    destroyAudioPlayer(*r_iter);
                    r_iter++;
                    vec->pop_back();
                }
            } else {
                vector<AudioPlayer *>::iterator iter;
                for (iter = vec->begin(); iter != vec->end(); ++iter) {
                    setSingleEffectState(*iter, state);
                }
            }
        } else {
            setSingleEffectState(*(vec->rbegin()), state);
        }
    }
}

void OpenSLEngine::setAllEffectState(int state) {
    EffectList::iterator p;
    for (p = sharedList().begin(); p != sharedList().end(); p++) {
        vector<AudioPlayer *> *vec = p->second;
        for (vector<AudioPlayer *>::iterator iter = vec->begin(); iter != vec->end(); ++iter) {
            setSingleEffectState(*iter, state);
        }
    }
}

void OpenSLEngine::resumeEffect(unsigned int effectID) {
    EffectList::iterator p = sharedList().find(effectID);
    if (p != sharedList().end()) {
        vector<AudioPlayer *> *vec = p->second;
        for (vector<AudioPlayer *>::iterator iter = vec->begin(); iter != vec->end(); ++iter) {
            resumeSingleEffect(*iter);
        }
    }
}

void OpenSLEngine::resumeAllEffects() {
    int state;
    EffectList::iterator p;
    for (p = sharedList().begin(); p != sharedList().end(); ++p) {
        vector<AudioPlayer *> *vec = p->second;
        for (vector<AudioPlayer *>::iterator iter = vec->begin(); iter != vec->end(); ++iter) {
            resumeSingleEffect(*iter);
        }
    }
}

void OpenSLEngine::setEffectLooping(unsigned int effectID, bool isLooping) {
    SLresult result;
    vector<AudioPlayer *> *vec = sharedList()[effectID];
    assert(NULL != vec);

    // get the first effect player that to be set loop config
    vector<AudioPlayer *>::iterator iter = vec->begin();
    AudioPlayer *player = *iter;

    if (player && player->fdPlayerSeek) {
        result = (*(player->fdPlayerSeek))->SetLoop(player->fdPlayerSeek, (SLboolean) isLooping, 0,
                                                    SL_TIME_UNKNOWN);
        assert(SL_RESULT_SUCCESS == result);
    }
}

void OpenSLEngine::setEffectsVolume(float volume) {
    assert(volume <= 1.0f && volume >= 0.0f);
    m_effectVolume = int(RANGE_VOLUME_MILLIBEL * volume) + MIN_VOLUME_MILLIBEL;

    SLresult result;
    EffectList::iterator p;
    AudioPlayer *player;
    for (p = sharedList().begin(); p != sharedList().end(); ++p) {
        vector<AudioPlayer *> *vec = p->second;
        for (vector<AudioPlayer *>::iterator iter = vec->begin(); iter != vec->end(); ++iter) {
            player = *iter;
            result = (*(player->fdPlayerVolume))->SetVolumeLevel(player->fdPlayerVolume,
                                                                 m_effectVolume);
            assert(SL_RESULT_SUCCESS == result);
        }
    }
}

float OpenSLEngine::getEffectsVolume() {
    float volume = (m_effectVolume - MIN_VOLUME_MILLIBEL) / (1.0f * RANGE_VOLUME_MILLIBEL);
    return volume;
}
